#!/usr/bin/env python3
"""
NOTE:
    run in python3
"""
# Python script to generate, check and insert hkrr.img
# Copyright Huawei Technologies Co., Ltd. 2021-2021.
##############################################################
import sys
import os
import argparse
import re
import struct
from collections import namedtuple
from collections import deque
import logging
import bisect
import traceback
import subprocess
from enum import Enum
import functools


def setup_logging():
    """
    NOTE:
        set up log file, log level and initiate LOGGER
    """
    if not ARGS.log:
        return
    logfile = 'hkrr.log'
    logging.basicConfig(filename=logfile, level=logging.INFO,
                        format='%(asctime)s %(name)s %(message)s')
    global LOGGER
    LOGGER = logging.getLogger('hkrr.py')
    LOGGER.setLevel(logging.DEBUG)
    LOGGER.info('run: %s, pid: %d', sys.argv[0], os.getpid())


def log_info(msg, **kwargs):
    """
    NOTE:
        log function
        1. msg: print message
        2. kwargs: print options
    """
    if ARGS.log:
        LOGGER.info(msg)
    logonly = kwargs.get('logonly', False)
    if logonly is False:
        print(msg)


def log_debug(msg, **kwargs):
    """
    NOTE:
        print message in log
        1. msg: target message to print
        2. kwargs: print options
    """
    if ARGS.log:
        LOGGER.debug(msg)
    logonly = kwargs.get('logonly', False)
    if logonly is False:
        print(msg)


def log_debug_hex(num):
    """
    NOTE:
        print a hex number in log
        1. num: target number to print
    """
    log_info('{:#x}'.format(num))


def add_and_align(x, add, a):
    """
    NOTE:
        add two numbers and align
        1. x: first number
        2. add: second number
        3. a: align bits
    """
    mask = a - 1
    return (x + add + mask) & ~mask


def alignp(fp, align):
    """
    NOTE:
        align file pointer
        1. fp: target file pointer to be aligned
        2. align: align bits
    """
    fp.seek(add_and_align(fp.tell(), 0, align))
    return fp.tell()


def roundup_mb(x):
    """
    NOTE:
        round up to MB
        1. x: target number to round
    """
    return (x + 0xfffff) >> 20


def roundup_kb(x):
    """
    NOTE:
        round up to KB
        1. x: target number to round
    """
    return (x + 0x3ff) >> 10


class ObjMapInfo:
    """
    NOTE:
        .o info in vmlinux.map
    """
    def __init__(self, addr, size, align, name, section):
        self.addr = addr
        self.size = size
        self.align = align
        self.name = name
        self.section = section


def match_o_line(line, pline=''):
    """
    NOTE:
        get .o info from line if match
        1. line: current line in vmlinux.map
    """
    if ARGS.lld:
        pattern = r'.*(0x\S+) +(\S+) +(\d+) +(\S+):\((.*)\)'
    else:
        pattern = r'.*(0x\S+)\s+(0x\S+)\s+(\S+).*'
    comp_pattern = re.compile(pattern)
    match_res = comp_pattern.match(line)
    if not match_res:
        return ObjMapInfo(None, None, None, None, None)

    if ARGS.lld:
        addr = int(match_res.group(1), base=16)
        size = int(match_res.group(2), base=16)
        align = int(match_res.group(3), base=10)
        name = match_res.group(4)
        section = match_res.group(5)
    else:
        addr = int(match_res.group(1), base=16)
        size = int(match_res.group(2), base=16)
        name = match_res.group(3)
        if pline != '':
            section = match_section(line, pline)
            align = get_align(name, section)
    obj_info = ObjMapInfo(addr, size, align, name, section)

    return obj_info


def match_section(line, pline):
    """
    NOTE:
        get section name from line if match
        1. line: current line in vmlinux.map
        2. pline: previous line in vmlinux.map
    """
    pattern_section1 = r' (\S+)\s+0x.*'
    pattern_section2 = r' (\S+)'
    sp1 = re.compile(pattern_section1)
    sp2 = re.compile(pattern_section2)
    sm = sp1.match(line)
    section = ''
    if sm:
        section = sm.group(1)
    else:
        # check prev line
        sm = sp2.match(pline)
        if sm:
            section = sm.group(1)
    return section


class ShType(Enum):
    """
    NOTE:
        all section types of elf
    """
    # section type
    HKRR_SHT_NULL = 0
    HKRR_SHT_PROGBITS = 1
    HKRR_SHT_SYMTAB = 2
    HKRR_SHT_STRTAB = 3
    HKRR_SHT_RELA = 4
    HKRR_SHT_HASH = 5
    HKRR_SHT_DYNAMIC = 6
    HKRR_SHT_NOTE = 7
    HKRR_SHT_NOBITS = 8
    HKRR_SHT_REL = 9
    HKRR_SHT_SHLIB = 10
    HKRR_SHT_DYNSYM = 11


class MyElf:
    """
    NOTE:
        class may be vmlinux or hkrr.img
    """
    def __init__(self, file, update2=False):
        self.elf_ident = namedtuple('elf_ident',
            'e_magic e_class e_data e_ver e_0 e_obits e_tbits e_pad ')

        # read Elf header
        self.file = file
        self.fp = None
        if update2:
            self.fp = open(file, 'r+b')
        else:
            self.fp = open(file, 'rb')
        self.elf_ident_s = struct.Struct('4sBBBBBB6s')
        data = self.fp.read(self.elf_ident_s.size)
        ident = self.elf_ident._make(self.elf_ident_s.unpack(data))

        if ident.e_magic != b'\x7fELF':
            log_info('Not ELF file')
            return
        self.elf32 = ident.e_class == 1
        if ident.e_data == 2:
            self.byteorder = 'big'
            self.en = '>'
        else:
            self.byteorder = 'little'
            self.en = '<'

        if ARGS.obits == 0:
            ARGS.obits = ident.e_obits
        if ARGS.tbits == 0:
            ARGS.tbits = ident.e_tbits

        self.elf_ehdr = namedtuple('elf_ehdr',
            'e_magic e_class e_data e_ver e_0 e_obits e_tbits e_pad '
            'e_type e_machine e_version e_entry e_phoff e_shoff '
            'e_flags e_ehsize e_phentsize e_phnum e_shentsize e_shnum '
            'e_shstrndx')
        self.elf_shdr = namedtuple('elf_shdr',
            'sh_name sh_type sh_flags sh_addr sh_offset sh_size sh_link '
            'sh_info sh_addralign sh_entsize')
        if self.elf32:
            self.addr_size = 4
            self.addr_fmt = 'i'
            self.elf_sym = namedtuple('elf_sym',
                'st_name st_value st_size st_info st_other st_shndx')
            self.elf_rela = namedtuple('elf_rela', 'r_offset r_info')
            self.elf_ehdr_s = struct.Struct(self.en + \
'4sBBBBBB6sHHIIIIIHHHHHH')
            self.elf_shdr_s = struct.Struct(self.en + 'IIIIIIIIII')
            self.elf_sym_s = struct.Struct(self.en + 'IIIBBH')
            self.elf_rela_s = struct.Struct(self.en + 'II')
            self.hkrr_sym_s = struct.Struct(self.en + 'IH')
            self.unwind_idx_s = struct.Struct(self.en + 'II')
        else:
            self.addr_size = 8
            self.addr_fmt = 'q'
            self.elf_sym = namedtuple('elf_sym',
                'st_name st_info st_other st_shndx st_value st_size')
            self.elf_rela = namedtuple('elf_rela', 'r_offset r_info r_addend')
            self.elf_ehdr_s = struct.Struct(self.en + \
'4sBBBBBB6sHHIQQQIHHHHHH')
            self.elf_shdr_s = struct.Struct(self.en + 'IIQQQQIIQQ')
            self.elf_sym_s = struct.Struct(self.en + 'IBBHQQ')
            self.elf_rela_s = struct.Struct(self.en + 'QQq')
            self.hkrr_sym_s = struct.Struct(self.en + 'QH')

        self.fp.seek(0)
        data = self.fp.read(self.elf_ehdr_s.size)
        self.eh = self.elf_ehdr._make(self.elf_ehdr_s.unpack(data))

        self.hkrr_rela = namedtuple('hkrr_rela', 'r_offset r_info')
        self.hkrr_objtab_d = namedtuple('hkrr_objtab',
            'o_offset o_size o_align o_name')
        self.hkrr_objtab_s_d = struct.Struct(self.en + 'IIII')
        if ARGS.debug:
            self.hkrr_objtab = self.hkrr_objtab_d
            self.hkrr_objtab_s = self.hkrr_objtab_s_d
        else:
            self.hkrr_objtab = namedtuple('hkrr_objtab',
                'o_offset o_size o_align')
            self.hkrr_objtab_s = struct.Struct(self.en + 'III')
        self.debugobjentsize = struct.calcsize(self.en + 'IIII')

        self.init_rela_types()
        self.init_vars()
        self.init_types()
        # load tables
        self.load_sh_strtab()
        self.load_sections()
        self.load_symtab()

    def __del__(self):
        if self.fp:
            self.fp.close()

    @staticmethod
    def get_string(strtab, offset):
        """
        NOTE:
            get symbol name
            1. strtab: string table
            2. offset: offset in the strtab
        """
        if not strtab:
            return 'unknown'
        return strtab[offset : strtab.find(b'\0', offset)].decode("utf-8")

    @staticmethod
    def pack_type_into_offs(r_offset, r_type):
        """
        NOTE:
            pack r_type into r_offset
        """
        high_bits = 32 - ARGS.tbits
        return (r_offset & ((1 << high_bits) - 1)) | (r_type << high_bits)

    @staticmethod
    def unpack_type_from_offs(packed_offset):
        """
        NOTE:
            unpack r_type form packed_offset and restore r_offset
        """
        high_bits = 32 - ARGS.tbits
        r_type = packed_offset >> high_bits
        r_offset = (packed_offset & ((1 << high_bits) - 1)) | (0xc << high_bits)
        return r_type, r_offset

    @staticmethod
    def get_bits(n):
        """
        NOTE:
            get bits of a binary number
            1. n: target number to compute its bits
        """
        msb = 0
        n >>= 1
        while n > 0:
            msb += 1
            n >>= 1
        return msb + 1

    @staticmethod
    def sign_extend(value, bits):
        """
        NOTE:
            this is a python style sign extend
            this is not as bit arythmetic
            1. value: target value to sign extend
            2. bits: number of bits to extend
        """
        sign_bit = 1 << (bits - 1)
        return (value & (sign_bit - 1)) - (value & sign_bit)

    @staticmethod
    def decode_imm2(insn, arr):
        """
        NOTE:
            get number from an instruction, combine two parts into one number
            1. insn: target instruction to extract the number
            2. arr included five following elems:
            (1) s2: the start bit of the second part
            (2) n2: number of bits of the second part
            (3) s1: the start bit of the first part
            (4) n1: number of bits of the first part
            (5) r1: left shift bits
        """
        v1 = ((insn >> arr[2]) & ((1 << arr[3]) - 1)) << arr[4]
        v2 = ((insn >> arr[0]) & ((1 << arr[1]) - 1)) << (arr[4] + arr[3])
        value = v1 | v2
        return value

    @staticmethod
    def min_relas_cmp(a, b):
        """
        NOTE:
            sort rela entries as rowner is the first prio,
            sowner is the second priority.
            a = { r_offset, rowner, sowner, flag, rvalue, rid }
        """
        if int(a[1]) != int(b[1]):
            return int(a[1]) - int(b[1])
        if int(a[2]) != int(b[2]):
            return int(a[2]) - int(b[2])
        return int(a[0]) - int(b[0])

    @staticmethod
    def get_offsets_from_kallsyms():
        """
        NOTE:
            get kallsyms offsets from file .rtos_vmlinux.kallsyms.S, it is
            used when kallsyms_offsets are extracted from vmlinux.
        """
        offsets = []
        need_record = False
        with open(".rtos_vmlinux.kallsyms.S", 'r') as fp:
            for line in fp:
                line = line.strip()
                if not line:
                    break
                if line == 'rtos_mod_kallsyms_offsets:':
                    need_record = True
                    continue
                if need_record:
                    off = line.split()[-1]
                    off = int(off, 16)
                    offsets.append(off)
        return offsets

    def has_rvalue(self, sowner, flag):
        """
        NOTE:
            return true if the rela entry has rvalue
        """
        has_value = flag & self.value
        has_value_owned = (sowner != 0 and (flag & self.value_owned))
        return has_value or has_value_owned

    def init_vars(self):
        """
        NOTE:
            initialize variables for latter methods.
        """
        self.objtab = None
        self.objstrtab = None
        self.outsymtab = None
        self.addrs = None
        self.version = None
        self.relas = None
        self.min_relas_all = None
        self.addends = None
        self.shs = None
        self.objs = None
        self.symtab = None
        self.strtab = None
        self.count = 0
        self.objstrtab_offset = 0
        self.objtab_offset = 0
        self.version_offset = 0
        self.reladynownertab_offset = 0
        self.tt_start = 0
        self.objentsize = 0
        self.unwindownertab_offset = 0
        self.__text_text_start = 0
        self.shs_offset = 0
        self.kasownertab_offset = 0
        self.__text_text_end = 0
        self.extra = 0
        self.strtab2_offset = 0
        self.shs_idx = 0
        self.symtab2_offset = 0
        self.objnum = 0
        self._text = 0
        self.addrs = None

    def init_types(self):
        """
        NOTE:
            compute rowner and sowner bytes
        """
        if ARGS.obits == 0:
            return
        log_info('Using {} bits per type and {} bits per object'
                .format(ARGS.tbits, ARGS.obits))
        self.rosize = (ARGS.tbits + 2 * ARGS.obits + 7) >> 3
        self.hkrr_rela_s = struct.Struct(self.en + 'I{}s'.format(self.rosize))
        if ARGS.minimize:
            self.hkrr_rela_s_min = struct.Struct(self.en + 'I')
        if self.elf32:
            self.hkrr_rela_v = struct.Struct(self.en +\
                                    'I{}x'.format(self.hkrr_rela_s.size - 4))
        else:
            self.hkrr_rela_v = struct.Struct(self.en +\
                                    'Q{}x'.format(self.hkrr_rela_s.size - 8))
        self.drosize = (2 * ARGS.obits + 7) >> 3
        self.kosize = (ARGS.obits + 7) >> 3

    def init_rela_types(self):
        """
        NOTE:
            initialize hkrr_rela_types
        """
        self.exclude = 0b0001        # exclude rela
        self.owned = 0b0010          # include rela if sym address may change
        self.value = 0b0100          # include value always
        self.value_owned = 0b1000    # include value if sym address may change

        # following ids matches to hkrr.c defined macros
        if self.elf32:
            hkrr_rela_names = {
                0: 'R_HKRR_NONE',
                1: 'R_HKRR_ABS32',
                2: 'R_HKRR_PREL',
                3: 'R_HKRR_BREL',
                4: 'R_HKRR_GOT_BREL',
                5: 'R_HKRR_PREL_24',
                6: 'R_HKRR_PREL_31',
                7: 'R_HKRR_MOVW_ABS_NC',
                8: 'R_HKRR_MOVT_ABS',
                9: 'R_HKRR_MOVW_PREL_NC',
                10: 'R_HKRR_MOVT_PREL',
                11: 'R_HKRR_PREL_VALUE'
            }
            self.elf_rela_types = {
                 1 : {'name' : 'R_ARM_PC24',
                       'id' : 5, 'flag' : 0},
                 2 : {'name' : 'R_ARM_ABS32',
                       'id' : 1, 'flag' : self.owned},
                 3 : {'name' : 'R_ARM_REL32',
                       'id' : 2, 'flag' : 0},
                23 : {'name' : 'R_ARM_RELATIVE',
                       'id' : 1, 'flag' : self.exclude},
                25 : {'name' : 'R_ARM_BASE_PREL',
                       'id' : 3, 'flag' : 0},
                26 : {'name' : 'R_ARM_GOT_BREL',
                       'id' : 4, 'flag' : self.exclude},
                28 : {'name' : 'R_ARM_CALL',
                       'id' : 5, 'flag' : 0},
                29 : {'name' : 'R_ARM_JUMP24',
                       'id' : 5, 'flag' : 0},
                38 : {'name' : 'R_ARM_TARGET1',
                       'id' : 1, 'flag' : self.owned},
                42 : {'name' : 'R_ARM_PREL31',
                       'id' : 6, 'flag' : 0},
                43 : {'name' : 'R_ARM_MOVW_ABS_NC',
                       'id' : 7, 'flag' : self.owned},
                44 : {'name' : 'R_ARM_MOVT_ABS',
                       'id' : 8, 'flag' : self.owned | self.value},
                45 : {'name' : 'R_ARM_MOVW_PREL_NC',
                       'id' : 9, 'flag' : 0},
                46 : {'name' : 'R_ARM_MOVT_PREL',
                       'id' : 10, 'flag' : self.value},
            }
        else:
            hkrr_rela_names = {
                0: 'R_HKRR_NONE',
                1: 'R_HKRR_ABS32',
                2: 'R_HKRR_ABS64',
                3: 'R_HKRR_PREL_32',
                4: 'R_HKRR_PREL_26',
                5: 'R_HKRR_PREL_LO19',
                6: 'R_HKRR_ADR_PREL_LO21',
                7: 'R_HKRR_ADR_PREL_PG_HI21',
                8: 'R_HKRR_LDST8_ABS_LO12_NC',
                9: 'R_HKRR_LDST16_ABS_LO12_NC',
                10: 'R_HKRR_LDST32_ABS_LO12_NC',
                11: 'R_HKRR_LDST64_ABS_LO12_NC',
                12: 'R_HKRR_ADR_GOT_PAGE',
                13: 'R_HKRR_LD64_GOT_LO12_NC',
                14: 'R_HKRR_PREL_64',
            }
            self.elf_rela_types = {
                258 : {'name' : 'R_AARCH64_ABS32',
                        'id' : 1, 'flag' : 0},
                257 : {'name' : 'R_AARCH64_ABS64',
                         'id' : 2, 'flag' : 0},
                260 : {'name' : 'R_AARCH64_PREL64',
                        'id' : 14, 'flag' : 0},
                261 : {'name' : 'R_AARCH64_PREL32',
                        'id' : 3, 'flag' : 0},
                282 : {'name' : 'R_AARCH64_JUMP26',
                        'id' : 4, 'flag' : 0},
                283 : {'name' : 'R_AARCH64_CALL26',
                        'id' : 4, 'flag' : 0},
                280 : {'name' : 'R_AARCH64_CONDBR19',
                        'id' : 5, 'flag' : 0},
                273 : {'name' : 'R_AARCH64_LD_PREL_LO19',
                        'id' : 5, 'flag' : 0},
                274 : {'name' : 'R_AARCH64_ADR_PREL_LO21',
                        'id' : 6, 'flag' : 0},
                275 : {'name' : 'R_AARCH64_ADR_PREL_PG_HI21',
                        'id' : 7, 'flag' : self.value_owned},
                277 : {'name' : 'R_AARCH64_ADD_ABS_LO12_NC',
                        'id' : 8, 'flag' : self.owned},
                278 : {'name' : 'R_AARCH64_LDST8_ABS_LO12_NC',
                        'id' : 8, 'flag' : self.owned},
                284 : {'name' : 'R_AARCH64_LDST16_ABS_LO12_NC',
                        'id' : 9, 'flag' : self.owned},
                285 : {'name' : 'R_AARCH64_LDST32_ABS_LO12_NC',
                        'id' : 10, 'flag' : self.owned},
                286 : {'name' : 'R_AARCH64_LDST64_ABS_LO12_NC',
                        'id' : 11, 'flag' : self.owned},
                311 : {'name' : 'R_AARCH64_ADR_GOT_PAGE',
                        'id' : 12, 'flag' : 0},
                312 : {'name' : 'R_AARCH64_LD64_GOT_LO12_NC',
                        'id' : 13, 'flag' : self.owned},
            }

        if ARGS.tbits == 0:
            ARGS.tbits = self.get_bits(len(hkrr_rela_names))

        self.hkrr_rela_types = {}

        for tid in self.elf_rela_types:
            ti = self.elf_rela_types[tid]
            rid = ti['id']
            ri = self.hkrr_rela_types[rid] = {}
            ri['name'] = hkrr_rela_names[rid]
            ri['flag'] = ti['flag']
        if ARGS.minimize:
            # id = 11 means a new hkrr rela type for minimize.
            # this does not related to an actual relocation types,
            # so need extra initialization
            ri = self.hkrr_rela_types[11] = {}
            ri['name'] = 'R_HKRR_PREL_VALUE'
            ri['id'] = 11
            ri['flag'] = 0

    def get_section_name(self, offset):
        """
        NOTE:
            get section name
            1. offset: the section offset in the shstrtab
        """
        return self.get_string(self.shstrtab, offset)

    def load_sections(self):
        """
        NOTE:
            load section headers
        """
        self.secs = []
        for i in range(0, self.e_shnum):
            self.fp.seek(self.eh.e_shoff + self.eh.e_shentsize * i)
            data = self.fp.read(self.elf_shdr_s.size)
            sh = self.elf_shdr._make(self.elf_shdr_s.unpack(data))
            val = (self.get_section_name(sh.sh_name), sh)
            self.secs.append(val)

    def print_sections(self):
        """
        NOTE:
            print sections of hkrr.img
        """
        for i, sec in enumerate(self.secs):
            log_info('{}, {}, {}'.format(sec[0], i, sec[1]))

    def get_section(self, name):
        """
        NOTE:
            get section of vmlinux through its name
            1. name: section name
        """
        for sec in self.secs:
            if sec[0] == name:
                return sec[1]
        return None

    def get_section_index(self, index):
        """
        NOTE:
            get section header
            1. index: section header index
        """
        return self.secs[index][1]

    def get_section_by_offset(self, r_offset):
        """
        NOTE:
            get section header
            1. r_offset: target offset to obtain section
        """
        sh = None
        shn = ''
        for name, sec in self.secs:
            if sec.sh_addr == 0:
                continue
            if r_offset < sec.sh_addr:
                break
            sh = sec
            shn = name
        if sh is None:
            log_info('res: {:#x}: {}, {:#x} {}'
                    .format(r_offset, shn, sh.sh_addr, sh))
            return ''
        return sh

    def load_sh_strtab(self):
        """
        NOTE:
            load section string table of vmlinux
        """
        # read section names table header
        self.e_shnum = self.eh.e_shnum
        e_shstrndx = self.eh.e_shstrndx
        if self.e_shnum == 0 and e_shstrndx == 0xffff:
            self.fp.seek(self.eh.e_shoff)
            data = self.fp.read(self.elf_shdr_s.size)
            sh = self.elf_shdr._make(self.elf_shdr_s.unpack(data))
            self.e_shnum = sh.sh_size
            e_shstrndx = sh.sh_link
        self.fp.seek(self.eh.e_shoff + self.eh.e_shentsize * e_shstrndx)
        data = self.fp.read(self.elf_shdr_s.size)
        sh = self.elf_shdr._make(self.elf_shdr_s.unpack(data))
        if sh.sh_type == ShType.HKRR_SHT_STRTAB.value:
            # read section content - names
            self.fp.seek(sh.sh_offset)
            self.shstrtab = self.fp.read(sh.sh_size)

    def get_symbol_address2(self, name):
        """
        NOTE:
            get symbol address
            1. name: symbol name
        """
        offset = 0
        end = len(self.symtab2)
        while offset < end:
            st_value, st_name = \
                self.hkrr_sym_s.unpack_from(self.symtab2, offset)
            n = self.get_string(self.strtab2, st_name)
            if n == name:
                return st_value
            offset += self.hkrr_sym_s.size
        log_info('No such symbol: {}'.format(name))
        return 0

    def load_symtab(self):
        """
        NOTE:
            load symbol table in vmlinux
        """
        self.symtab2 = None
        self.strtab2 = None
        sh = self.get_section('.symtab')
        if not sh:
            log_info('No symtab - error')
            return
        if sh.sh_type != ShType.HKRR_SHT_SYMTAB.value:
            return
        self.symentsize = sh.sh_entsize
        if self.symentsize == self.elf_sym_s.size:
            # ELF symtab
            self.symtab2 = bytearray()
            self.strtab2 = bytearray()
            self.load_elf_symtab(sh)
            return
        self.fp.seek(sh.sh_offset)
        self.symtab2 = self.fp.read(sh.sh_size)
        sth = self.get_section_index(sh.sh_link)
        if sth.sh_type == ShType.HKRR_SHT_STRTAB.value:
            # read section content - names
            self.fp.seek(sth.sh_offset)
            self.strtab2 = self.fp.read(sth.sh_size)
        self._text = self.get_symbol_address2('_text')
        self.__text_text_start = self.get_symbol_address2('__text_text_start')
        self.__text_text_end = self.get_symbol_address2('__text_text_end')
        log_info('_text: {:#x}, __text_text_start: {:#x}, '
                 '__text_text_end: {:#x}'.format(self._text,
                 self.__text_text_start, self.__text_text_end))

    def load_elf_symtab(self, sh):
        """
        NOTE:
            load elf symbol table
            1. sh: symbol table section header
        """
        self.symtab = None
        self.strtab = None
        self.fp.seek(sh.sh_offset)
        self.symtab = self.fp.read(sh.sh_size)
        sth = self.get_section_index(sh.sh_link)
        if sth.sh_type == ShType.HKRR_SHT_STRTAB.value:
            # read section content - names
            self.fp.seek(sth.sh_offset)
            self.strtab = self.fp.read(sth.sh_size)

    def get_symbol_data(self, index):
        """
        NOTE:
            get symbol data from elf
            1. index: symbol index in symbol table
        """
        begin = self.symentsize * index
        data = self.symtab[begin : begin + self.symentsize]
        sym = self.elf_sym._make(self.elf_sym_s.unpack(data))
        return sym

    def get_symbol_name(self, offset):
        """
        NOTE:
            get symbol name from elf
            1. offset: symbol name offset in strtab of elf
        """
        return self.get_string(self.strtab, offset)

    def get_symbol_address(self, name, info=0):
        """
        NOTE:
            get symbol address of vmlinux
            1. name: symbol name
            2. info: symbol info, include symbol type and symbol string index
        """
        offset = 0
        end = len(self.symtab)
        num = 0
        while offset < end:
            sym = self.elf_sym._make(
                    self.elf_sym_s.unpack_from(self.symtab, offset))
            n = self.get_symbol_name(sym.st_name)
            if n == name:
                if info == 1:
                    return sym.st_value, sym.st_shndx
                if info == 2:
                    return sym.st_value, sym.st_name
                return sym.st_value
            offset += self.symentsize
            num += 1
        return 0

    def get_symbol_value(self, name, size, off=0):
        """
        NOTE:
            get symbol value
            1. name: symbol name
            2. size: symbol size
            3. off: offset of the target value
        """
        addr, sec = self.get_symbol_address(name, info=1)
        sh = self.get_section_index(sec)
        offset = addr - sh.sh_addr + sh.sh_offset + off
        self.fp.seek(offset)
        return self.fp.read(size)

    def get_symbol_value_int(self, name, size, off=0):
        """
        NOTE:
            get integer symbol value
            1. name: symbol name
            2. size: symbol size
            3. off: offset of the target value
        """
        data = self.get_symbol_value(name, size, off)
        return int.from_bytes(data, byteorder=self.byteorder, signed=False)

    def get_symbol_value_string(self, name, size, off=0):
        """
        NOTE:
            get string symbol value
            1. name: symbol name
            2. size: symbol size
            3. off: offset of the target symbol
        """
        data = self.get_symbol_value(name, size, off)
        return data[0 : data.find(b'\0')].decode('utf-8')

    def print_relas(self):
        """
        NOTE:
            print reloc items in .rela.tab in hkrr.img
        """
        sh = self.get_section('.rel.text' if self.elf32 else '.rela.text')
        self.fp.seek(sh.sh_offset)
        data = self.fp.read(sh.sh_size)
        offset = 0
        end = sh.sh_size
        entsize = self.hkrr_rela_s_min.size if ARGS.minimize else self.elf_rela_s.size
        num = 0
        extra = 0
        self.load_objects()
        self.relas = []
        while offset < end:
            if ARGS.minimize:
                packed_offset, = self.hkrr_rela_s_min.unpack_from(data, offset)
                r_type, r_offset = self.unpack_type_from_offs(packed_offset)
                rowner = 0
                sowner = 0
            else:
                rela = self.hkrr_rela._make(
                        self.hkrr_rela_s.unpack_from(data, offset))
                r_info = int.from_bytes(rela.r_info, \
                                    byteorder=self.byteorder, signed=False)
                r_type = r_info >> (2 * ARGS.obits)
                omask = (1 << ARGS.obits) - 1
                rowner = r_info & omask
                sowner = (r_info >> ARGS.obits) & omask
                r_offset = rela.r_offset
            ri = self.hkrr_rela_types.get(r_type)
            rtype = ri['name']
            flag = ri['flag']
            r = bisect.bisect_left(self.relas, r_offset)
            if r != len(self.relas) and self.relas[r] == r_offset:
                # for some reason there are duplicated entries in
                # .ARM.unwind_idx, skip them
                # there also strange relocs for other sections (ld bug?)
                # skip them as well
                log_info('duplicated rela entry {} found: {:#x}'\
                        .format(r, r_offset), logonly=True)
                offset += entsize
                continue
            if not self.elf32:
                r_offset += self._text
            r_value = 0
            has_minimize_value = (ARGS.minimize and rtype == 'R_HKRR_PREL_VALUE')
            if self.has_rvalue(sowner, flag) or has_minimize_value:
                offset += entsize if ARGS.minimize else sh.sh_entsize
                r_value, = self.hkrr_rela_v.unpack_from(data, offset)
                if ARGS.minimize:
                    offset += self.hkrr_rela_v.size - entsize
                extra += 1
            if ARGS.debug:
                roname = self.get_object_name(rowner)
                soname = self.get_object_name(sowner)
                log_info('{}: offset = {:#x}, type = {:#x} ({}), '
                         'value = {:#x}, rowner = {}, sowner = {}, '
                         'roname = {}, soname = {}'
                        .format(num, r_offset, r_type, rtype, r_value,
                            rowner, sowner, roname, soname))
            else:
                log_info('{}: offset = {:#x}, type = {:#x} ({}), '
                         'value = {:#x}, rowner = {}, sowner = {}'
                         .format(num, r_offset, r_type, rtype, r_value,
                            rowner, sowner))
            if ARGS.minimize:
                self.relas.insert(r, r_offset)
            else:
                self.relas.insert(r, rela.r_offset)
            offset += entsize if ARGS.minimize else sh.sh_entsize
            num += 1
        log_info('relocation entries: {}'.format(num))
        log_info('value entries: {}'.format(extra))

    def get_object_name(self, index):
        """
        NOTE:
            get .o file name
            1. index: the .o file index in objtab of hkrr.img
        """
        offset = index * self.objentsize
        obj = self.hkrr_objtab._make(
                self.hkrr_objtab_s.unpack_from(self.objtab, offset))
        return self.get_string(self.objstrtab, obj.o_name)

    def get_object(self, index):
        """
        NOTE:
            get object from .objtab in hkrr.img
            1. index: object index
        """
        offset = index * self.objentsize
        obj = self.hkrr_objtab._make(
                self.hkrr_objtab_s.unpack_from(self.objtab, offset))
        return obj

    def load_objects(self):
        """
        NOTE:
            load objects in .objtab
        """
        if self.objtab:
            return
        sh = self.get_section('.objtab')
        if sh is None:
            log_info('No .objtab section')
            return
        self.fp.seek(sh.sh_offset)
        self.objtab = self.fp.read(sh.sh_size)
        self.objentsize = sh.sh_entsize
        sh = self.get_section('.objstrtab')
        if sh is None:
            log_info('No .objstrtab section')
            return
        self.fp.seek(sh.sh_offset)
        self.objstrtab = self.fp.read(sh.sh_size)
        if self.objentsize == self.debugobjentsize:
            ARGS.debug = True
            self.hkrr_objtab = self.hkrr_objtab_d
            self.hkrr_objtab_s = self.hkrr_objtab_s_d

    def print_objects(self):
        """
        NOTE:
            print all randomized .o files
        """
        self.load_objects()
        offset = 0
        end = len(self.objtab)
        num = 0
        align = 0
        while offset < end:
            obj = self.hkrr_objtab._make(
                    self.hkrr_objtab_s.unpack_from(self.objtab, offset))
            o_offset = obj.o_offset
            if num != 0 and not self.elf32:
                o_offset += self._text
            if ARGS.debug:
                name = self.get_string(self.objstrtab, obj.o_name)
                log_info('{:>6}, offset = {:#x}, size = {:#x}, align = {}, '
                    'name = {}'
                    .format(num, o_offset, obj.o_size, obj.o_align, name))
            else:
                log_info('{:>6}, offset = {:#x}, size = {:#x}, align = {}'
                        .format(num, o_offset, obj.o_size, obj.o_align))
            offset += self.objentsize
            align += (obj.o_align - 1)
            num += 1
        log_info('max alignment = {}'.format(align))

    def get_max_alignment(self):
        """
        NOTE:
            get max align bits of all .o files in objtab in hkrr.img
        """
        self.load_objects()
        offset = 0
        end = len(self.objtab)
        align = 0
        while offset < end:
            obj = self.hkrr_objtab._make(
                    self.hkrr_objtab_s.unpack_from(self.objtab, offset))
            offset += self.objentsize
            align += (obj.o_align - 1)
        return align

    def build_objtab(self, mapfile):
        """
        NOTE:
            build obj table of hkrr.img
            1. mapfile:  vmlinux.map
        """
        if ARGS.debug:
            oe = self.hkrr_objtab(o_offset=0, o_size=0, o_align=0, o_name=0)
        else:
            oe = self.hkrr_objtab(o_offset=0, o_size=0, o_align=0)
        data = self.hkrr_objtab_s.pack(*oe)
        self.objentsize = len(data)
        self.objtab = bytearray(data)
        self.objstrtab = bytearray(b'\0')
        self.objs = [(0, 0)]
        i = 0
        poe = None

        with open(mapfile, 'r') as fp:
            pline = ''
            nameoffset = 1
            for line in fp:
                obj_info = match_o_line(line, pline)
                addr = obj_info.addr
                size = obj_info.size
                align = obj_info.align
                name = obj_info.name
                if addr is None:
                    pline = line
                    continue

                # we select only object which will be randomized
                if (addr < self.__text_text_start or
                    addr >= self.__text_text_end) or size == 0:
                    continue

                if name == 'linker':
                    # linker stub - merge with previous object
                    size = addr + size - (poe.o_offset + self._text)
                    oe = poe._replace(o_size=size)
                    self.hkrr_objtab_s.pack_into(self.objtab, i * self.objentsize, *oe)
                    continue

                o_offset = addr if self.elf32 else addr - self._text
                if ARGS.debug:
                    oe = self.hkrr_objtab(o_offset=o_offset,
                            o_size=size, o_align=align, o_name=nameoffset)
                    name += '\0'
                    self.objstrtab += name.encode('utf-8')
                    nameoffset += len(name)
                else:
                    oe = self.hkrr_objtab(o_offset=o_offset,
                            o_size=size, o_align=align)
                data = self.hkrr_objtab_s.pack(*oe)
                self.objtab += data
                self.objs.append((addr, size))
                i += 1
                poe = oe
                pline = line

        log_info('Number of objects: {}'.format(i))
        if not self.__check_and_init_obits(i):
            return
        self.addrs = list((r[0] for r in self.objs))

    def find_object(self, addr):
        """
        NOTE:
            get .o file in vmlinux
            1. addr:  the address of the .o file
        """
        if addr == 0:
            return 0
        index = bisect.bisect_right(self.addrs, addr)
        if not index:
            log_info('object not found: {:#x}'.format(addr))
            return 0
        index -= 1
        if addr >= (self.objs[index][0] + self.objs[index][1]):
            return 0
        return index

    def print_symbols(self):
        """
        NOTE:
            print symbols in elf
        """
        offset = 0
        num = 0
        end = len(self.symtab2)
        while offset < end:
            st_value, st_name = \
                self.hkrr_sym_s.unpack_from(self.symtab2, offset)
            name = self.get_string(self.strtab2, st_name)
            log_info('{:#2}: value = {:#018x}, name = {}'
                     .format(num, st_value, name))
            offset += self.hkrr_sym_s.size
            num += 1

    def add_sym(self, name, value):
        """
        NOTE:
            add symbol in symbol table and symbol string table of hkrr.img
            1. name:  symbol name
            2. value: symbol value
        """
        self.symtab2 += self.hkrr_sym_s.pack(value, len(self.strtab2))
        self.strtab2 += name.encode('utf-8') + b'\0'

    def append_symtab_builtin(self):
        """
        NOTE:
            append symbol table in hkrr.img
        """
        self.add_sym('', 0)
        self.add_sym('_text', self.get_symbol_address('_text'))
        self.add_sym('_end', self.get_symbol_address('_end'))
        self.add_sym('__text_text_start',
                self.get_symbol_address('__text_text_start'))
        self.add_sym('__text_text_end',
                self.get_symbol_address('__text_text_end'))
        self.add_sym('__text_text_end2',
                self.get_symbol_address('__text_text_end2'))
        self.add_sym('__hkrr_data_start',
                self.get_symbol_address('__hkrr_data_start'))
        self.add_sym('__hkrr_data_end',
                self.get_symbol_address('__hkrr_data_end'))
        self.add_sym('__hkrr_table_start',
                self.get_symbol_address('__hkrr_table_start'))
        self.add_sym('__hkrr_table_end',
                self.get_symbol_address('__hkrr_table_end'))
        self.add_sym('kallsyms_names',
                self.get_symbol_address('kallsyms_names'))
        self.add_sym('kallsyms_markers',
                self.get_symbol_address('kallsyms_markers'))
        sh = self.get_section('.init.data')
        self.add_sym('init_data_offset', sh.sh_offset)
        self.add_sym('init_data_addr', sh.sh_addr)

    def append_symtab_nobuiltin(self):
        """
        NOTE:
            add symbol table in nobuiltin condition
        """
        self.add_sym('kallsyms_num_syms',
                self.get_symbol_address('kallsyms_num_syms'))
        self.add_sym('kallsyms_relative_base',
                self.get_symbol_address('kallsyms_relative_base'))
        self.add_sym('kallsyms_offsets',
                self.get_symbol_address('kallsyms_offsets'))
        self.add_sym('init_uts_ns', self.get_symbol_address('init_uts_ns'))
        self.add_sym('__rela_offset',
                self.get_symbol_address('__rela_offset'))
        self.add_sym('__rela_size', self.get_symbol_address('__rela_size'))

    def append_symtab(self, fp):
        """
        NOTE:
            append symbol table in hkrr.img
            1. fp: the file handle of hkrr.img
        """
        log_info('Generating symbol table')
        self.append_symtab_builtin()
        if not ARGS.builtin:
            self.append_symtab_nobuiltin()
        release = self.get_symbol_value_string('init_uts_ns', 64, 134)
        version = self.get_symbol_value_string('init_uts_ns', 64, 199)
        self.version = release + ' ' + version + '\0'
        log_info('Linux version: {}'.format(self.version))
        self._text = self.get_symbol_address2('_text')
        self.__text_text_start = self.get_symbol_address2('__text_text_start')
        self.__text_text_end = self.get_symbol_address2('__text_text_end')
        log_info('_text: {:#x}, __text_text_start: {:#x}, '
                 '__text_text_end: {:#x}'
                 .format(self._text, self.__text_text_start,
                     self.__text_text_end))
        # align
        offset = alignp(fp, 8)
        size = fp.write(self.symtab2)
        # sh_name sh_type sh_flags sh_addr sh_offset sh_size sh_link
        # sh_info sh_addralign sh_entsize
        sh = self.elf_shdr(self.symtab2_offset, 2, 0, 0, offset, size,
                            self.count + 1, 0, 8, self.hkrr_sym_s.size)
        self.elf_shdr_s.pack_into(self.shs, self.shs_offset, *sh)
        self.shs_offset += self.elf_shdr_s.size
        self.count += 1
        # strtab
        # align
        offset = alignp(fp, 8)
        size = fp.write(self.strtab2)
        # sh_name sh_type sh_flags sh_addr sh_offset sh_size sh_link
        # sh_info sh_addralign sh_entsize
        sh = self.elf_shdr(self.strtab2_offset, 3, 0, 0, offset, size,
                            0, 0 , 1, 0)
        self.elf_shdr_s.pack_into(self.shs, self.shs_offset, *sh)
        self.shs_offset += self.elf_shdr_s.size
        self.count += 1

    def read_addends(self, file):
        """
        NOTE:
            read addends of all relocation items
            1. file: the addend file produced in compile process
        """
        self.addends = {}
        if file is None:
            return
        log_info('reading addends: r_offset r_addend')
        p = re.compile(r'.*(0x\S+)\s+(-?0x\S+).*')
        with open(file, 'r') as fp:
            for line in fp:
                m = p.match(line)
                if not m:
                    continue
                r_offset = int(m.group(1), base=16)
                r_addend = int(m.group(2), base=16)
                self.addends[r_offset] = r_addend
        log_info('added {} addends'.format(len(self.addends)))

    def decode_imm(self, insn, s1, n1, r1):
        """
        NOTE:
            get number from an instruction
            1. insn: target instruction to extract the number
            2. s1: start bit
            3. n1: number of continous bits to get
            4. r1: left shift bits
        """
        value = ((insn >> s1) & ((1 << n1) - 1)) << r1
        return self.sign_extend(value, n1 + r1)

    def get_rela(self, relas2, offset):
        """
        NOTE:
            get relocation item, type and target symbol from an reloc item
            1. relas2: target .rel.xxx section
            2. offset: the offset of the reloc item in relas2
        """
        rela = self.elf_rela._make(
            self.elf_rela_s.unpack_from(relas2, offset))
        if self.elf32:
            r_type = rela.r_info & 0xff
            r_sym = rela.r_info >> 8
        else:
            r_type = rela.r_info & 0xffffffff
            r_sym = rela.r_info >> 32
        sym = self.get_symbol_data(r_sym)
        return rela, r_type, sym

    def get_rel32_value(self, r_offset, data, sym):
        """
        NOTE:
            get symbol value of rel32 reloc
            1. r_offset: reloc instruction address
            2. data: instruction (little or big endian)
            3. sym: target symbol of reloc item
        """
        value = 0
        if sym.st_info == 3:
            # symbol address is a section address
            sh = self.get_section_index(sym.st_shndx)
            if sh.sh_flags & 0x4:
                # if section is a code
                value = r_offset + self.sign_extend(data, 32)
        else:
            value = sym.st_value
        return value

    def get_movwt_value(self, rtype, r_offset, movw_insn, movt_insn):
        """
        NOTE:
            get symbol value and rvalue in movw and movt instructions
            1. rtype: relocation type
            2. r_offset: the target instruction address
            3. movw_insn: movw instruction
            4. movt_insn: movt instruction
        """
        movw_arr = (16, 4, 0, 12, 0)
        movt_arr = (16, 4, 0, 12, 16)
        movw_imm = self.decode_imm2(movw_insn, movw_arr)
        movt_imm = self.decode_imm2(movt_insn, movt_arr)
        rvalue = movt_imm | movw_imm
        if rtype in ('R_ARM_MOVW_ABS_NC', 'R_ARM_MOVT_ABS'):
            value = rvalue
        elif rtype == 'R_ARM_MOVW_PREL_NC':
            value = r_offset + (movt_imm | movw_imm) + 0x10
            value = (value + 0x100000000) & (0x100000000 - 1)
        elif rtype == 'R_ARM_MOVT_PREL':
            value = r_offset + (movt_imm | movw_imm) + 0xc
            value = (value + 0x100000000) & (0x100000000 - 1)
        else:
            return 0, 0
        return value, rvalue

    def get_rela_value(self, sec, r_offset, r_type, sym):
        """
        NOTE:
            get value and rvalue of an reloc item
            1. sec: section header info
            2. r_offset: target reloc instruction address
            3. r_type: reloc type
            4. sym: target reloc symbol address
        """
        if sec != 0:
            sh = self.get_section_index(sec)
        else:
            sh = self.get_section_by_offset(r_offset)
        if sh is None or sh == '':
            return 0, 0
        offset = r_offset - sh.sh_addr + sh.sh_offset
        self.fp.seek(offset)
        # instruction is 4 bytes
        buf = self.fp.read(4)
        insn = int.from_bytes(buf, byteorder='little', signed=False)
        data = int.from_bytes(buf, byteorder=self.byteorder, signed=False)
        value = 0
        rvalue = 0
        rtype = self.elf_rela_types[r_type]['name']
        if rtype in ('R_ARM_ABS32', 'R_ARM_RELATIVE', 'R_ARM_TARGET1'):
            value = data
        elif rtype in ('R_ARM_PC24', 'R_ARM_CALL', 'R_ARM_JUMP24'):
            value = r_offset + self.decode_imm(insn, 0, 24, 2) + 8
        elif rtype in ('R_ARM_MOVW_ABS_NC', 'R_ARM_MOVW_PREL_NC'):
            self.fp.seek(offset + 4)
            movt_insn = int.from_bytes(self.fp.read(4), byteorder='little', signed=False)
            value, rvalue = self.get_movwt_value(rtype, r_offset, insn, movt_insn)
        elif rtype in ('R_ARM_MOVT_ABS', 'R_ARM_MOVT_PREL'):
            self.fp.seek(offset - 4)
            movw_insn = int.from_bytes(self.fp.read(4), byteorder='little', signed=False)
            value, rvalue = self.get_movwt_value(rtype, r_offset, movw_insn, insn)
        elif rtype == 'R_ARM_REL32':
            value = self.get_rel32_value(r_offset, data, sym)
        elif rtype == 'R_ARM_PREL31':
            value = r_offset + self.sign_extend(data, 31)
        elif rtype == 'R_ARM_BASE_PREL':
            value = 0
        elif rtype == 'R_ARM_GOT_BREL':
            traceback.print_stack()
            return 0, 0
        else:
            log_info("unhandled: r: {}, l: {:#x}: i: {:#x}, v: {:#x}, \
s: {:#x}".format(r_type, r_offset, insn, value, sym.st_value))
            return 0, 0

        return value, rvalue

    def get_values(self, r_type, sh, rela, sym):
        """
        NOTE:
            get value and rvalue of an reloc item
            1. r_type: reloc type
            2. sh: section header of .rel.tab in hkrr.img
            3. rela: relocation item
            4. sym: target reloc symbol address
        """
        will_continue = False
        ri = self.elf_rela_types[r_type]
        flag = ri['flag']
        if flag == self.exclude:
            will_continue = True
            return will_continue, None, None
        if self.elf32:
            value, rvalue = self.get_rela_value(sh.sh_info, \
                                rela.r_offset, r_type, sym)
        else:
            value = rvalue = sym.st_value + rela.r_addend
        if ((rela.r_offset < self.__text_text_start or
             rela.r_offset >= self.__text_text_end) and
            (value < self.__text_text_start or
                            value >= self.__text_text_end)):
            # may be check for __text_text_start sym.st_name
            # to avoid extra relocation for hkrr.c itself
            will_continue = True
            return will_continue, None, None
        return will_continue, value, rvalue

    def get_owners(self, value, rela, sym, r_type):
        """
        NOTE:
            get sowner and rowner
            1. value: value of an reloc item
            2. rela: relocation item
            3. sym: target reloc symbol address
            4. r_type: reloc type
        """
        will_continue = False
        ri = self.elf_rela_types[r_type]
        flag = ri['flag']
        sowner = self.find_object(value)
        if sowner == 0:
            log_info('rela sym owner not found: r_offset = {:#x}, '
                     'value: {:#010x}, name: {}'.format(rela.r_offset,
                     value, self.get_symbol_name(sym.st_name)),
                     logonly=True)
            if flag & self.owned:
                will_continue = True
                return will_continue, None, None
        rowner = self.find_object(rela.r_offset)
        if rowner == 0:
            log_info('rela owner not found: r_offset = {:#x}'
                     .format(rela.r_offset), logonly=True)
        if sowner == 0 and rowner == 0:
            log_info('both owners not found: {}: r_offset = {:#x}, '
                     'value: {:#x}, rvalue, sym: {:#x}, name: {}'
                     .format(r_type, rela.r_offset, value,
                     sym.st_value, self.get_symbol_name(sym.st_name)),
                     logonly=False)
            if not self.elf32:
                log_info('both owners not found, check logs')
                will_continue = True
                return will_continue, None, None
        return will_continue, sowner, rowner

    def read_relas(self, sh):
        """
        NOTE:
            read relas in hkrr.img
            1. sh: section header of .rel.tab in hkrr.img
        """
        self.fp.seek(sh.sh_offset)
        data = self.fp.read(sh.sh_size)
        offset = 0
        end = len(data)
        entsize = self.elf_rela_s.size
        ret_relas = bytearray()
        kaddr = 0x80000000 if self.elf32 else 0x8000000000000000
        while offset < end:
            rela, r_type, sym = self.get_rela(data, offset)
            if sym.st_name == self.tt_start:
                # skip __text_text_start marker
                offset += entsize
                continue
            if r_type == 0 or rela.r_offset < kaddr:
                # for arm32 there are some strange relocations for
                # __aeabi_unwind_cpp_pr0, but R_ARM_NONE
                # also some low addresses - skip it
                log_info('strange relocation: {:#x} of type {}'
                    .format(rela.r_offset, r_type), logonly=True)
                offset += entsize
                continue
            if (not self.elf32 and
                (rela.r_addend > 0xffffffff or rela.r_addend < -0xffffffff)):
                log_info("r_addend is larger than int32_t: {:#x}"\
                        .format(rela.r_addend))
                return None

            will_continue, value, rvalue = self.get_values(r_type, sh, rela, sym)
            if will_continue:
                offset += entsize
                continue

            ri = self.elf_rela_types[r_type]
            flag = ri['flag']
            r_offset = rela.r_offset
            r = bisect.bisect_left(self.relas, r_offset)
            if r != len(self.relas) and self.relas[r] == r_offset:
                log_info('duplicated rela entry {} found: {:#x} == {:#x}'
                        .format(r, self.relas[r], r_offset), logonly=True)
                offset += entsize
                continue
            if not self.elf32:
                r_offset -= self._text

            will_continue, sowner, rowner = self.get_owners(value, rela, sym, r_type)
            if will_continue:
                offset += entsize
                continue

            rid = ri['id']
            if ARGS.minimize and rid == 2 and sowner != 0:
                # rid = 2 means this is a R_HKRR_PREL type,
                # but if this reloc entry has sowner, we can not calculate it's sowner
                # by r_offset and insn, so we need to store it's sowner into hkrr.img.
                # with a new hkrr reloc type (rid = 11, name = 'R_HKRR_PREL_VALUE'),
                # can carry an rvalue msg (which actually is the sowner)
                rid = 11
                rvalue = value
            if rid >= (1 << ARGS.tbits):
                log_info('Type id does not fit to allocated bits')
                log_info('Increase ARGS.tbits and HKRR_BITS_PER_TYPE')
                return None
            r_info = int((rid << (2 * ARGS.obits)) \
                     | (sowner << ARGS.obits) \
                     | rowner
                     ).to_bytes(self.rosize, byteorder=self.byteorder)
            rentry = self.hkrr_rela(r_offset=r_offset, r_info=r_info)
            ret_relas += self.hkrr_rela_s.pack(*rentry)
            has_minimize_value = (ARGS.minimize and rid == 11)
            if self.has_rvalue(sowner, flag) or has_minimize_value:
                ret_relas += self.hkrr_rela_v.pack(rvalue)
                self.extra += 1
            self.relas.insert(r, rela.r_offset)
            if ARGS.minimize:
                self.min_relas_all.append((r_offset, rowner, sowner, flag, rvalue, rid))
            offset += entsize
        return ret_relas

    def build_min_relatab_data(self):
        """
        NOTE:
            build minimize relatab data, need sort all rela entry
        """
        data = bytearray()
        self.min_relas_all.sort(key=functools.cmp_to_key(self.min_relas_cmp))
        for rela_obj in self.min_relas_all:
            # rela_obj's elements is { r_offset, rowner, sowner, flag, rvalue, rid }
            packed_offset = self.pack_type_into_offs(rela_obj[0], rela_obj[5])
            data += self.hkrr_rela_s_min.pack(packed_offset)
            if self.has_rvalue(rela_obj[2], rela_obj[3]) or \
                (rela_obj[5] == 11):
                data += self.hkrr_rela_v.pack(rela_obj[4])
        return data

    def check_if_not_rela_section(self, name):
        """
        NOTE:
            check if this section is not a rela section
        """
        if self.elf32:
            if name[:4] != '.rel' or name[:10] == '.rel.debug':
                return True
            if name in ('.rel', '.rel.dyn'):
                return True
            if ARGS.unwind is True and name == '.rel.ARM.unwind_idx':
                return True
        else:
            if name[:5] != '.rela' or name[:11] == '.rela.debug':
                return True
            if name in ('.rela', '.rela.dyn'):
                return True
        return False


    def append_relatab(self, fp):
        """
        NOTE:
            add rela table in hkrr.img
            1. fp: file handle of hkrr.img
        """
        log_info('Generating relocation table')
        self.read_addends(ARGS.addends)
        _addr, self.tt_start = self.get_symbol_address('__text_text_start', \
info=2)
        offset = alignp(fp, 8)
        size = 0
        log_info('add relocation data to offset {}'.format(offset))
        self.relas = []
        self.extra = 0
        if ARGS.minimize:
            self.min_relas_all = []
        for _i, (name, sh) in enumerate(self.secs):
            if self.check_if_not_rela_section(name):
                continue
            data = self.read_relas(sh)
            if len(data) == 0:
                continue
            if not ARGS.minimize:
                size += fp.write(data)
            if name in ('.rela.text', '.rel.text'):
                sh1 = sh

        if ARGS.minimize:
            data = self.build_min_relatab_data()
            size += fp.write(data)

        if ARGS.minimize:
            # hkrr_rela_s, hkrr_rela_v have 64bits; hkrr_rela_s_min has 32 bits
            log_info('added {} relocation entries'
                 .format(size // self.hkrr_rela_s_min.size -
                 self.extra * 2))
        else:
            log_info('added {} relocation entries'
                 .format(size // self.hkrr_rela_s.size -
                 self.extra))
        log_info('added {} value entries'.format(self.extra))
        if size == 0:
            log_info('no relocation entrie, something is wrong...')
            return
        if ARGS.minimize:
            sh2 = sh1._replace(sh_offset=offset,
                            sh_size=size - self.extra * self.hkrr_rela_s_min.size,
                            sh_entsize=self.hkrr_rela_s_min.size,
                            sh_link=self.count + 1, sh_info=0)
        else:
            sh2 = sh1._replace(sh_offset=offset, sh_size=size,
                            sh_entsize=self.hkrr_rela_s.size,
                            sh_link=self.count + 1, sh_info=0)
        self.elf_shdr_s.pack_into(self.shs, self.shs_offset, *sh2)
        self.shs_offset += self.elf_shdr_s.size
        self.count += 1

    def append_shstrtab(self, fp):
        """
        NOTE:
            append section string table
            1. fp: file pointer of hkrr.img
        """
        offset = alignp(fp, 8)
        sh_offset = offset
        sh = self.get_section('.shstrtab')
        fp.write(self.shstrtab)
        offset += len(self.shstrtab)
        # ADD additional section names to shstrtab
        self.symtab2_offset = offset - sh_offset
        size = fp.write('.symtab\0'.encode('utf-8'))
        offset += size
        self.strtab2_offset = offset - sh_offset
        size = fp.write('.strtab\0'.encode('utf-8'))
        offset += size
        self.objtab_offset = offset - sh_offset
        size = fp.write('.objtab\0'.encode('utf-8'))
        offset += size
        self.objstrtab_offset = offset - sh_offset
        size = fp.write('.objstrtab\0'.encode('utf-8'))
        offset += size
        if not ARGS.builtin:
            self.reladynownertab_offset = offset - sh_offset
            size = fp.write('.reladynownertab\0'.encode('utf-8'))
            offset += size
        self.kasownertab_offset = offset - sh_offset
        size = fp.write('.kasownertab\0'.encode('utf-8'))
        offset += size
        if ARGS.unwind:
            self.unwindownertab_offset = offset - sh_offset
            size = fp.write('.unwindownertab\0'.encode('utf-8'))
            offset += size
        self.version_offset = offset - sh_offset
        size = fp.write('.version\0'.encode('utf-8'))
        offset += size
        sh2 = sh._replace(sh_offset=sh_offset, sh_size=offset-sh_offset)
        self.elf_shdr_s.pack_into(self.shs, self.shs_offset, *sh2)
        self.shs_offset += self.elf_shdr_s.size
        self.shs_idx = self.count
        self.count += 1

    def append_objtab(self, fp, objmap2):
        """
        NOTE:
            append obj table in hkrr.img
            1. fp: file handle of hkrr.img
            2. objmap2: file name of vmlinux.map
        """
        log_info('Generating object table')
        offset = alignp(fp, 8)
        self.build_objtab(objmap2)
        size = fp.write(self.objtab)
        # sh_name sh_type sh_flags sh_addr sh_offset sh_size sh_link
        # sh_info sh_addralign sh_entsize
        sh2 = self.elf_shdr(self.objtab_offset, 0x6000001, 0, 0,
                            offset, size, 0, 0 , 8, self.objentsize)
        self.elf_shdr_s.pack_into(self.shs, self.shs_offset, *sh2)
        self.shs_offset += self.elf_shdr_s.size
        self.count += 1

    def append_objstrtab(self, fp):
        """
        NOTE:
            append obj string table in hkrr.img
            1. fp: file pointer of hkrr.img
        """
        offset = alignp(fp, 8)
        size = fp.write(self.objstrtab)
        sh2 = self.elf_shdr(self.objstrtab_offset, 3, 0, 0,
                            offset, size, 0, 0 , 1, 0)
        self.elf_shdr_s.pack_into(self.shs, self.shs_offset, *sh2)
        self.shs_offset += self.elf_shdr_s.size
        self.count += 1

    def append_reladynownertab(self, fp):
        """
        NOTE:
            add owner table of reloc items in .rel.dyn or .rela.dyn
            1: file handle of hkrr.img
        """
        log_info('Generating rela.dyn owners table')
        offset = alignp(fp, 8)
        sh = self.get_section('.rela')
        if not sh:
            sh = self.get_section('.rela.dyn')
        if not sh:
            log_info('rela.dyn section not found')
            return
        self.fp.seek(sh.sh_offset)
        data = self.fp.read(sh.sh_size)
        off = 0
        num = 0
        size = 0
        while off < len(data):
            rela = self.elf_rela._make(self.elf_rela_s.unpack_from(data, off))
            rtype = rela.r_info & 0xffffffff
            sowner = 0
            rowner = 0
            if rtype == 1027:
                value = (1 << 64) + rela.r_addend
                sowner = self.find_object(value)
                if sowner == 0:
                    log_info('rela sym owner not found: {}: r_offset = {:#x}, '
                             'value: {:#010x}'
                             .format(num, rela.r_offset, value), logonly=True)
                rowner = self.find_object(rela.r_offset)
                if rowner == 0:
                    log_info('rela owner not found: {}: r_offset = {:#x}'
                             .format(num, rela.r_offset), logonly=True)
            else:
                log_info('rela.dyn rela type is wrong: {}, skipping...'
                         .format(rtype), logonly=True)
            r_info = int((sowner << ARGS.obits) | rowner) \
                        .to_bytes(self.drosize, byteorder=self.byteorder)
            size += fp.write(r_info)
            off += self.elf_rela_s.size
            num += 1
        log_info('added {} rela.dyn owner entries'.format(num))
        # sh_name sh_type sh_flags sh_addr sh_offset sh_size sh_link
        # sh_info sh_addralign sh_entsize
        sh2 = self.elf_shdr(self.reladynownertab_offset, 0x6000004, 0, 0,
                            offset, size, 0, 0 , 8, self.drosize)
        self.elf_shdr_s.pack_into(self.shs, self.shs_offset, *sh2)
        self.shs_offset += self.elf_shdr_s.size
        self.count += 1

    def get_offsets_from_vmlinux(self):
        """
        NOTE:
            get kallsyms offsets from vmlinux
        """
        offsets = []
        num = self.get_symbol_value_int('kallsyms_num_syms', 4)
        log_info('Number of kallsyms: {}'.format(num))
        data = self.get_symbol_value('kallsyms_offsets', num * 4)
        pos = 0
        while pos < len(data):
            off = int.from_bytes(data[pos : pos + 4],
                                byteorder=self.byteorder, signed=False)
            offsets.append(off)
            pos += 4
        return offsets

    def get_base_from_file(self, filename):
        """
        NOTE:
            get kallsyms base from file .rtos_vmlinux.kallsyms2.S, it is
            used when kallsyms_relative_base are extracted from vmlinux.
        """
        base = self.get_symbol_address2('_text')
        pm = ''
        offstr = ''
        with open(filename, 'r') as fp:
            pattern_relative_base = r'\s+PTR\s+_text\s+(\S+)\s+(\S+).*'
            p = re.compile(pattern_relative_base)
            for line in fp:
                m = p.match(line)
                if m:
                    pm = m.group(1)
                    offstr = m.group(2)
                    break
        fp.close()
        if pm == '':
            return base
        if offstr != '0':
            pattern_offset = r'0x(\S+)'
            p = re.compile(pattern_offset)
            m = p.match(offstr)
            offval = int(m.group(1), base=16)
            if pm == '+':
                base += offval
            else:
                base -= offval
        return base

    def append_kasownertab(self, fp):
        """
        NOTE:
            append kallsyms owner table in hkrr.img
            1. fp: file pointer of hkrr.img
        """
        log_info('Generating kallsyms owners table')
        offset = alignp(fp, 8)
        base = self.get_symbol_value_int('kallsyms_relative_base', \
                                        self.addr_size)
        if base == 0:
            # no-apply-dynamic-relocs leaves variable empty
            # use '_text'
            base = self.get_base_from_file('.tmp_vmlinux.kallsyms2.S')
        log_info('kallsyms base address: {:#0x}'.format(base))
        if ARGS.extractkallsyms:
            kallsyms_offsets = self.get_offsets_from_kallsyms()
        else:
            kallsyms_offsets = self.get_offsets_from_vmlinux()
        size = 0
        num = 0
        for off in kallsyms_offsets:
            addr = base + off
            owner = self.find_object(addr)
            if owner == 0:
                log_info('kas owner not found: {}: addr = {:#x}'
                        .format(num, addr), logonly=True)
            kowner = owner.to_bytes(self.kosize, byteorder=self.byteorder)
            size += fp.write(kowner)
            num += 1
        sh2 = self.elf_shdr(self.kasownertab_offset, 0x6000005, 0, 0,
                            offset, size, 0, 0 , 8, 2)
        self.elf_shdr_s.pack_into(self.shs, self.shs_offset, *sh2)
        self.shs_offset += self.elf_shdr_s.size
        self.count += 1

    def append_unwindownertab(self, fp):
        """
        NOTE:
            append unwind owner table in hkrr.img
            1. fp: the file handle of hkrr.img
        """
        log_info('Generating unwind owners table')
        offset = alignp(fp, 8)
        sh = self.get_section('.ARM.unwind_idx')
        if not sh:
            log_info('.ARM.unwind_idx section not found')
            return
        base = sh.sh_addr
        log_info('unwind_idx start {:#x}'.format(base))
        self.fp.seek(sh.sh_offset)
        data = self.fp.read(sh.sh_size)
        off = 0
        num = 0
        size = 0
        while off < len(data):
            prel31, _ = self.unwind_idx_s.unpack_from(data, off)
            addr = base + off + self.sign_extend(prel31, 31)
            owner = self.find_object(addr)
            if owner == 0:
                log_info('unwind_idx owner not found: {}: addr = {:#x}'
                        .format(num, addr), logonly=True)
            kowner = owner.to_bytes(self.kosize, byteorder=self.byteorder)
            size += fp.write(kowner)
            off += self.unwind_idx_s.size
            num += 1
        log_info('added {} unwind_idx owner entries'.format(num))
        # sh_name sh_type sh_flags sh_addr sh_offset sh_size sh_link
        # sh_info sh_addralign sh_entsize
        sh2 = self.elf_shdr(self.unwindownertab_offset, 0x6000007, 0, 0,
                            offset, size, 0, 0 , 8, self.kosize)
        self.elf_shdr_s.pack_into(self.shs, self.shs_offset, *sh2)
        self.shs_offset += self.elf_shdr_s.size
        self.count += 1

    def append_version(self, fp):
        """
        NOTE:
            append version section
            1. fp: file pointer of hkrr.img
        """
        offset = alignp(fp, 8)
        size = fp.write(self.version.encode('utf-8'))
        sh2 = self.elf_shdr(self.version_offset, 0x6000006, 0, 0,
                            offset, size, 0, 0 , 1, 0)
        self.elf_shdr_s.pack_into(self.shs, self.shs_offset, *sh2)
        self.shs_offset += self.elf_shdr_s.size
        self.count += 1

    def append_secheaders(self, fp):
        """
        NOTE:
            append section headers in hkrr.img
            1. fp: file pointer of hkrr.img
        """
        # write section headers
        sh_offset = alignp(fp, 8)
        shs = self.shs[:self.shs_offset]
        fp.write(shs)
        # write elf header
        fp.seek(0)
        # e_ident e_type e_machine e_version e_entry e_phoff e_shoff
        # e_flags e_ehsize e_phentsize e_phnum e_shentsize e_shnum e_shstrndx
        eh2 = self.eh._replace(e_obits=ARGS.obits, e_tbits=ARGS.tbits, \
                    e_phoff=0, e_shoff=sh_offset, e_phentsize=0, \
                    e_phnum=0, e_shnum=self.count, e_shstrndx=self.shs_idx)
        data = self.elf_ehdr_s.pack(*eh2)
        fp.write(data)

    def generate_image(self, vmlinux2, objmap2, output):
        """
        NOTE:
            generate hkrr.img
            1. vmlinux2: vmlinux
            2. objmap2: vmlinux.map
            3. output: hkrr.img
        """
        log_info('Building relocation information from "{}" and "{}" to "{}"'
                 .format(vmlinux2, objmap2, output))
        self.count = 1
        self.shs = bytearray(4096)
        # fist section is null
        self.shs_offset = self.elf_shdr_s.size
        image = os.open(output, os.O_TRUNC | os.O_CREAT, 0o640)
        os.close(image)
        with open(output, 'r+b') as fp:
            # skip elf header, written later
            fp.seek(self.elf_ehdr_s.size)
            #.shstrtab
            self.append_shstrtab(fp)
            #.symtab2
            self.append_symtab(fp)
            #.objtab
            self.append_objtab(fp, objmap2)
            #.objstrtab
            if ARGS.debug:
                self.append_objstrtab(fp)
            #.rela.text
            self.append_relatab(fp)
            #.owner.rela.dyn
            if not ARGS.builtin:
                self.append_reladynownertab(fp)
            # .kasownertab
            if ARGS.kallsyms:
                self.append_kasownertab(fp)
            # .unwindownertab
            if ARGS.unwind:
                self.append_unwindownertab(fp)
            # version
            self.append_version(fp)
            # append section headers
            self.append_secheaders(fp)
            return True
        return False

    def check_text_size(self):
        """
        NOTE:
            check if new text size after randomized exceeded
            CONFIG_RTOS_HKRR_EXTRA_TEXT_SIZE
        """
        text_end = self.get_symbol_address2('__text_text_end')
        text_end2 = self.get_symbol_address2('__text_text_end2')
        max_text_pad = roundup_kb(self.get_max_alignment())
        text_pad = roundup_kb(text_end2 - text_end)
        log_info('max text alignment: {} (KiB), hkrr reserved text size: \
{} (KiB)'.format(max_text_pad, text_pad))
        if max_text_pad > text_pad:
            log_info('hkrr reserved text size is smaller than '
                    'possible text alignment\n'
                    'increase CONFIG_HKRR_EXTRA_TEXT_SIZE')
            return False
        return True

    def check_table_size(self, hkrrimg):
        """
        NOTE:
            check if hkrr table size exceeded
            CONFIG_HKRR_TABLE_SIZE
        """
        table_start = self.get_symbol_address2('__hkrr_table_start')
        table_end = self.get_symbol_address2('__hkrr_table_end')
        table_size = roundup_kb(table_end - table_start)
        krelocs_img_size = roundup_kb(os.path.getsize(hkrrimg))
        log_info('hkrr table image size: {} (KiB), hkrr reserved table '
                 'memory size: {} (KiB)'.format(krelocs_img_size, table_size))
        if krelocs_img_size > table_size:
            log_info('hkrr reserved table memory size is smaller than '
                    'hkrr table image size')
            log_info('increase CONFIG_HKRR_TABLE_SIZE')
            return False
        return True

    def check_data_size(self):
        """
        NOTE:
            check if hkrr data size exceeded CONFIG_RTOS_HKRR_DATA_SIZE
        """
        data_start = self.get_symbol_address2('__hkrr_data_start')
        # configured for dynamic memory
        if data_start == 0:
            return True
        data_end = self.get_symbol_address2('__hkrr_data_end')
        data_size = roundup_kb(data_end - data_start)
        sh = self.get_section('.objtab')
        objs_count = sh.sh_size // sh.sh_entsize
        log_info('Number of objects: {}'.format(objs_count - 1))
        size = 0
        size = add_and_align(size, 8 * objs_count, 16)
        if ARGS.kallsyms:
            sh = self.get_section('.kasownertab')
            kas_count = sh.sh_size // sh.sh_entsize
            log_info('kas_count: {}'.format(kas_count))
            kallsyms_names = self.get_symbol_address2('kallsyms_names')
            kallsyms_markers = self.get_symbol_address2('kallsyms_markers')
            kas_names_size = kallsyms_markers - kallsyms_names
            size = add_and_align(size, 8 * kas_count, 16)
            size = add_and_align(size, kas_names_size, 16)
        size = roundup_kb(size)
        log_info('data size: {} (KiB), hkrr reserved data memory size: \
{} (KiB)'.format(size, data_size))
        if size > data_size:
            log_info('hkkr reserved data memory size is smaller than data \
size')
            log_info('increase CONFIG_RTOS_HKRR_DATA_SIZE')
            return False
        return True

    def insert_image(self, hkrrimg, inimage):
        """
        NOTE:
            insert hkrr.img into vmlinux
            1. hkrrimg: hkrr.img
            2. inimage: input image to be inserted into
        """
        log_info('Insert relocation information "{}" into "{}"'
                .format(hkrrimg, inimage))

        if self.check_text_size() is False:
            return False

        if self.check_table_size(hkrrimg) is False:
            return False

        if self.check_data_size() is False:
            return False

        hkrr_table_start = self.get_symbol_address2('__hkrr_table_start')
        init_data_addr = self.get_symbol_address2('init_data_addr')
        init_data_offset = self.get_symbol_address2('init_data_offset')
        table_offset = init_data_offset + hkrr_table_start - init_data_addr
        log_info('__hkrr_table image offset: {}'.format(table_offset))

        with open(hkrrimg, 'rb') as ifp:
            with open(inimage, 'r+b') as ofp:
                ifp.seek(0, 0)
                data = ifp.read()
                ofp.seek(table_offset)
                ofp.write(data)
                return True
        return False

    def save_symtab(self):
        """
        NOTE:
            save symbol table in vmlinux, it's used in update_symtab
        """
        sh = self.get_section('.symtab')
        if sh.sh_type != ShType.HKRR_SHT_SYMTAB.value:
            return
        self.fp.seek(sh.sh_offset)
        self.fp.write(self.outsymtab)

    def build_search_map(self):
        """
        NOTE:
            build .o files search map
        """
        self.load_objects()
        self.objs = []
        offset = 0
        self.objnum = len(self.objtab) // self.objentsize
        num = 0
        while offset < len(self.objtab):
            obj = self.hkrr_objtab._make(
                    self.hkrr_objtab_s.unpack_from(self.objtab, offset))
            o_offset = obj.o_offset
            if num != 0 and not self.elf32:
                o_offset += self._text
            self.objs.append((o_offset, obj.o_size))
            offset += self.objentsize
            num += 1
        self.addrs = list((r[0] for r in self.objs))

    def update_symtab(self, objtab, objmap2):
        """
        NOTE:
            upadte the symbol table of vmlinux according to .o files offsets
            1. objtab: hkrr.img
            2. objmap2: the table records the file offsets
        """
        log_info('Updating vmlinux symbol table')
        objtab = MyElf(objtab)
        objtab.build_search_map()
        with open(objmap2, 'rb') as fp:
            objmap2 = fp.read()
        mapnum = len(objmap2)//self.addr_size
        if objtab.objnum != mapnum:
            log_info('object table size is not equal object map size: {} != {}'
                    .format(objtab.objnum, mapnum))
            return
        log_info('object table size equals object map size: {} == {}'
                .format(objtab.objnum-1, mapnum-1))
        self.outsymtab = bytearray()
        offset = 0
        num = 0
        # iterate all symbols
        while (offset + self.elf_sym_s.size) <= len(self.symtab):
            sym = self.elf_sym._make(
                    self.elf_sym_s.unpack_from(self.symtab, offset))
            owner = objtab.find_object(sym.st_value)
            if owner == 0 and sym.st_value != 0:
                log_info('sym owner not found: {}: value: {:#010x}, name: {}'
                        .format(num, sym.st_value,
                            self.get_symbol_name(sym.st_name)), logonly=True)
            obj_offset, = struct.unpack_from(self.en + self.addr_fmt, objmap2,
                                             owner * self.addr_size)
            value = sym.st_value + obj_offset
            sym2 = sym._replace(st_value=value)
            self.outsymtab += self.elf_sym_s.pack(*sym2)
            offset += self.symentsize
            num += 1
        self.save_symtab()

    def restore_objtab(self, start, step, objmap2):
        """
        NOTE:
            restore the obj table in runtime vmlinux according to start and
            step.
            1. start: the first .o file to randomize
            2. step: the randomize step
            3. objmap2: the result table to record the offsets of all .o files
        """
        log_info('Restore vmlinux symbol table')
        self.load_objects()
        self.objnum = len(self.objtab)//self.objentsize
        image = os.open(objmap2, os.O_TRUNC | os.O_CREAT, 0o640)
        os.close(image)
        fp = open(objmap2, 'r+b')
        offset = self.__text_text_start
        j = 0
        while j < step:
            overflow = False
            i = start + j
            while True:
                if i >= self.objnum:
                    i -= self.objnum
                    overflow = True
                if overflow and i >= start:
                    break
                obj = self.get_object(i)
                if obj.o_size == 0 or obj.o_align == 0xffff:
                    i += step
                    continue
                fp.seek(i * 4)
                if obj.o_align != 0:
                    mask = obj.o_align - 1
                    offset = (offset + mask) & ~mask
                fp.write((offset - obj.o_offset).to_bytes(4, \
                            byteorder=self.byteorder, signed=True))
                i += step
                offset += obj.o_size
            j += 1
        fp.close()

    def __check_and_init_obits(self, nums):
        """
        NOTE:
            if ARGS.obits is not initialized, init it.
            or if arg.obits is given by input param, check if it ok,
            if not, return False, otherwize return True
        """
        if ARGS.obits == 0:
            ARGS.obits = self.get_bits(nums)
            self.init_types()
        if nums >= (1 << ARGS.obits):
            log_info('Object id does not fit to allocated bits')
            log_info('Pass larger --obits or use automatic detection')
            return False
        return True


def objs2():
    """
    NOTE:
        print all randomize .o files, the difference between objs and objs2 is
        that: objs worked on hkrr.img while objs2 worked on vmlinux.map
    """
    i = 1
    output = False
    pattern_bfd_sym = r'.*0x\S+\s+(\S+).*'
    with open(ARGS.map, 'r') as fp:
        patsym = pattern_bfd_sym
        sp = re.compile(patsym)
        pline = ''
        for line in fp:
            s = sp.match(line)
            if s:
                name = s.group(1)
                if name == '__text_text_start':
                    output = True
                elif name == '__text_text_end':
                    return
            if output is False:
                continue

            obj_info = match_o_line(line, pline)
            if obj_info.addr is None:
                pline = line
                continue
            if obj_info.size == 0:
                continue

            log_info('{}: {: >#018x} {: >#08x} {: >#06x} {} [{}]'
                     .format(i, obj_info.addr, obj_info.size, obj_info.align, obj_info.name,
                             obj_info.section))
            i += 1
            pline = line


def check():
    """
    NOTE:
        check if there are some symbols have the same address with the next
        .o file, it worked on vmlinux.map
    """
    output = False
    with open(ARGS.file, 'r') as fp:
        sp1 = re.compile(r'.*0x\S+\s+(\S+).*')
        sp2 = re.compile(r'\s*(0x\S+)\s+([a-zA-Z0-9_]+).*')
        lines = deque(['', '', ''], 3)
        pline = ''
        for line in fp:
            s = sp1.match(line)
            if s:
                name = s.group(1)
                if name == '__text_text_start':
                    output = True
                elif name == '__text_text_end':
                    return
            if output is False:
                continue
            lines.append(line)
            obj_info = match_o_line(line)
            addr = obj_info.addr
            if addr is None:
                continue
            sm = sp2.match(lines[-2])
            saddr = 0
            sname = ''
            pl = False
            if not sm:
                sm = sp2.match(lines[-3])
                if sm:
                    pl = True
            if sm:
                saddr = int(sm.group(1), base=16)
                sname = sm.group(2)
            if saddr != 0 and sname != '':
                if addr == saddr:
                    print('SYMBOL:\t\t{:#018x}\t\t  {}'.format(saddr, sname))
                    print(pline, end='')
                    if pl:
                        print(lines[-3], end='')
                    print(lines[-2], end='')
                    print(line)
            pline = line


def syms():
    """
    NOTE:
        command to print symbols in hkrr.img
    """
    elf = MyElf(ARGS.file)
    elf.print_symbols()


def relas():
    """
    NOTE:
        command to print relas in hkrr.img
    """
    elf = MyElf(ARGS.file)
    elf.print_relas()


def secs():
    """
    NOTE:
        print sections in hkrr.img
    """
    elf = MyElf(ARGS.file)
    elf.print_sections()


def objs():
    """
    NOTE:
        command to print all random .o files in hkrr.img
    """
    elf = MyElf(ARGS.file)
    elf.print_objects()


def generate():
    """
    NOTE:
        command to generate hkrr.img
    """
    elf = MyElf(ARGS.vmlinux)
    return elf.generate_image(ARGS.vmlinux, ARGS.map, ARGS.output)


def insert():
    """
    NOTE:
        command to insert hkrr.img
    """
    elf = MyElf(ARGS.hkrr)
    return elf.insert_image(ARGS.hkrr, ARGS.inimage)


def update():
    """
    NOTE:
        command to update symbol table in vmlinux
    """
    elf = MyElf(ARGS.vmlinux, True)
    elf.update_symtab(ARGS.hkrr, ARGS.objmap)


def restore():
    """
    NOTE:
        command to restore the runtime vmlinux
    """
    elf = MyElf(ARGS.hkrr)
    objmap2 = 'objmap.bin'
    start = int(ARGS.start)
    step = int(ARGS.step)
    elf.restore_objtab(start, step, objmap2)
    out = subprocess.call(['objcopy', '-g', ARGS.vmlinux, ARGS.outimage], \
            shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    if out != 0:
        log_info('objcopy vmlinux failed')
        return
    elf2 = MyElf(ARGS.outimage, True)
    elf2.update_symtab(ARGS.hkrr, objmap2)


def get_align(name, section):
    """
    NOTE:
        get align bits of a symbol
        1. name: symbol name
        2. section: the section of the symbol
        return :
            0 - means that the align value cannot be obtained
            8 - is default align
            others - is real align of the .o file
    """
    if name == 'linker':
        return 0
    if not hasattr(ARGS, "align"):
        return 0
    if ARGS.align:
        if not os.path.exists(name):
            return 0
        elf = MyElf(name)
        sh = elf.get_section(section)
        if sh is None:
            return 0
        return sh.sh_addralign
    return 8


def add_debug_argparser(parser, subparsers):
    """
    NOTE:
        add all debug params into argparser
    """
    parser.add_argument('--debug', help='add object and symbol names to table',
                        action='store_true')
    # syms
    sub_parser = subparsers.add_parser('syms', help='syms help')
    sub_parser.add_argument('file')
    sub_parser.set_defaults(func=syms)
    # relas
    sub_parser = subparsers.add_parser('relas', help='relas help')
    sub_parser.add_argument('file')
    sub_parser.set_defaults(func=relas)
    # secs
    sub_parser = subparsers.add_parser('secs', help='secs help')
    sub_parser.add_argument('file')
    sub_parser.set_defaults(func=secs)
    # objs
    sub_parser = subparsers.add_parser('objs', help='objs help')
    sub_parser.add_argument('file')
    sub_parser.set_defaults(func=objs)
    # objs2
    sub_parser = subparsers.add_parser('objs2', help='print all randomize .o files, it works '
                                                     'on vmlinux.map, and append option --lld '
                                                     'option if vmlinux.map is lld format')
    sub_parser.add_argument('map')
    sub_parser.set_defaults(func=objs2)
    # update symtab
    sub_parser = subparsers.add_parser('update',
                                help='update symtab based on object map')
    sub_parser.add_argument('hkrr')
    sub_parser.add_argument('objmap')
    sub_parser.add_argument('vmlinux')
    sub_parser.set_defaults(func=update)
    # restore symtab
    sub_parser = subparsers.add_parser('restore',
                        help='restore symtab based on params start and step')
    sub_parser.add_argument('vmlinux')
    sub_parser.add_argument('hkrr')
    sub_parser.add_argument('start')
    sub_parser.add_argument('step')
    sub_parser.add_argument('outimage')
    sub_parser.set_defaults(func=restore)
    # check
    sub_parser = subparsers.add_parser('check', help='check if there are some symbols have the '
                                                     'same address with the next .o file, it '
                                                     'works on vmlinux.map, and append option '
                                                     '--lld if vmlinux.map is lld format')
    sub_parser.add_argument('file')
    sub_parser.set_defaults(func=check)


def add_main_argparser(parser, subparsers):
    """
    NOTE:
        add all main function params into argparser
    """

    parser.add_argument('--log', action='store_true', help='log output')
    parser.add_argument('--builtin', help='kernel self-randomization support',
                        action='store_true')
    parser.add_argument('--kallsyms', help='add kallsyms info',
                        action='store_true')
    parser.add_argument('--extractkallsyms', help='extract kallsyms from \
vmlinux', action='store_true')
    parser.add_argument('--unwind', help='add unwind info',
                        action='store_true')
    parser.add_argument('--minimize', help='minimize hkrr.img by removing r_info',
                        action='store_true')
    parser.add_argument('--lld', help='use the map file in lld format',
                        action='store_true')
    parser.add_argument('--obits', type=int, default=0, help='bits per object')
    parser.add_argument('--tbits', type=int, default=0, help='bits per type')
    # generate
    sp = subparsers.add_parser('generate', help='generate help')
    sp.add_argument('--align', help='read alignment from object',
                    action='store_true')
    sp.add_argument('--prefix',
                    help='merge sections/objects using section prefix',
                    default=False, action='store_true')
    sp.add_argument('--addends', help='read relocation addends from file')
    sp.add_argument('vmlinux')
    sp.add_argument('map')
    sp.add_argument('output')
    sp.set_defaults(func=generate)
    # insert
    sp = subparsers.add_parser('insert', help='insert help')
    sp.add_argument('hkrr')
    sp.add_argument('inimage')
    sp.set_defaults(func=insert)


def main():
    """
    NOTE:
        main function of python, define parsers and commands
    """
    python_version = sys.version_info.major
    if python_version != 3:
        log_info('Please use python3 to run')
        sys.exit(1)
    parser = argparse.ArgumentParser()
    subparsers = parser.add_subparsers(help='sub-command help')
    add_main_argparser(parser, subparsers)
    add_debug_argparser(parser, subparsers)
    # parse ARGS
    global ARGS
    ARGS = parser.parse_args()
    setup_logging()
    if not vars(ARGS) or not hasattr(ARGS, 'func'):
        parser.print_help()
        sys.exit(1)
    if ARGS.func() is False:
        sys.exit(1)

if __name__ == '__main__':
    main()
